#include "fd_chan.h"

#include "evt_loop.h"
#include "os_socket.h"

namespace qio {

class FdChanCallbacks {
 public:
  static void OnFdEvents(uv_poll_t *handle, int status,
                         int events) QIO_NOEXCEPT {
    auto *fd_chan = static_cast<FdChan *>(handle->data);
    assert(fd_chan != nullptr);
    if (status < 0) {
      // todo add error_callback
      printf("OnFdEvents error %s\n", uv_err_name(status));
      if (fd_chan->error_cb_) {
        fd_chan->error_cb_(fd_chan, status, uv_err_name(status));
      }
      return;
    }
    if (fd_chan->rw_cb_) {
      fd_chan->rw_cb_(fd_chan, fd_chan->GetFd(), events);
    }
  }

  static void OnFdClosed(uv_handle_t *handle) QIO_NOEXCEPT {
    auto *fd_chan = static_cast<FdChan *>(handle->data);
    assert(fd_chan != nullptr);
    // close fd
    socket_close(fd_chan->fd_);
    // notify
    if (fd_chan->closed_cb_) {
      fd_chan->closed_cb_(fd_chan);
    }
    // dereference
    fd_chan->rw_cb_ = nullptr;
    fd_chan->closed_cb_ = nullptr;
    fd_chan->error_cb_ = nullptr;
  }
};

FdChan::~FdChan() QIO_NOEXCEPT {  // NOLINT
  delete poll_;
}

FdChan::FdChan(EventLoop *loop, Fd fd) QIO_NOEXCEPT : loop_(loop),
                                                      poll_(new uv_poll_t),
                                                      events_(DISABLE),
                                                      fd_(fd),
                                                      rw_cb_(nullptr),
                                                      closed_cb_(nullptr),
                                                      error_cb_(nullptr) {
  assert(loop != nullptr);
  assert(fd != INVALID_SOCKET);
  poll_->data = this;
#ifdef __APPLE__
  int no_sig_pipe_value = 1;
  socket_setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE,
                    reinterpret_cast<void *>(&no_sig_pipe_value),
                    sizeof no_sig_pipe_value);
#endif
}

void FdChan::Start() QIO_NOEXCEPT {
  assert(loop_->IsInLoop());
  uv_poll_init_socket(loop_->GetRawLoop(), poll_, fd_);
}

void FdChan::Close() QIO_NOEXCEPT {
  assert(loop_->IsInLoop());
  DisableReadWrite();
  uv_close(reinterpret_cast<uv_handle_t *>(poll_),
           &FdChanCallbacks::OnFdClosed);
}

void FdChan::UpdateEvents() QIO_NOEXCEPT {
  assert(loop_->IsInLoop());
  if (events_ == DISABLE) {
    uv_poll_stop(poll_);
    return;
  }
  uv_poll_start(poll_, events_, &FdChanCallbacks::OnFdEvents);
}

void FdChan::EnableWrite() QIO_NOEXCEPT {
  const auto old_events = events_;
  events_ |= WRITEABLE;  // NOLINT
  if (old_events != events_) {
    UpdateEvents();
  }
}

void FdChan::EnableRead() QIO_NOEXCEPT {
  const auto old_events = events_;
  events_ |= READABLE;  // NOLINT
  if (old_events != events_) {
    UpdateEvents();
  }
}

void FdChan::DisableWrite() QIO_NOEXCEPT {
  const auto old_events = events_;
  events_ &= (~WRITEABLE);  // NOLINT
  if (old_events != events_) {
    UpdateEvents();
  }
}

void FdChan::DisableRead() QIO_NOEXCEPT {
  const auto old_events = events_;
  events_ &= (~READABLE);  // NOLINT
  if (old_events != events_) {
    UpdateEvents();
  }
}

void FdChan::EnableReadWrite() QIO_NOEXCEPT {
  constexpr auto v = READABLE | WRITEABLE;
  if (events_ != v) {
    events_ = v;
    UpdateEvents();
  }
}

void FdChan::DisableReadWrite() QIO_NOEXCEPT {
  constexpr auto v = DISABLE;
  if (v != events_) {
    events_ = v;
    UpdateEvents();
  }
}

}  // namespace qio
